package BNS.plumbing;

import java.util.Enumeration;
import java.util.Random;
import java.util.Vector;

import javax.swing.JOptionPane;

import BNS.graphs.Node;

/** This class handles the assignment of nodes to clients.
	It implements a random assignment policy, but is intended to be overridden
	to implement any other policy, including the specific assignments used on
	previous occasions (which is useful for making movies of past games).
	*/
public class LiveManager extends Manager {
	
	protected BNSGameController game;
	protected SessionHost host;
	private Vector<ClientLiaison> list=null;  
	private Random rand;

	/** Creates a LiveManager.
		@param h the SessionHost that oversees the operations.
		@param g the BNSGame that this Manager will be responsible for supporting.
		*/
	public LiveManager(SessionHost h, BNSGameController g) throws java.rmi.RemoteException {
		host= h;
		game= g;
		timeLimit= game.getTimeLimit()*1000;
		clock= new Timing();
		System.err.println("LiveManager created");
		}
	
	public Timing getClock() { return clock; }
	
	/** starts the thread that reads the event file and drives the simulated game events */
	public void run() { 
		System.err.println("timeoutThread.start()ed"); 
		clock.waitUntil("timeout", timeLimit); 
		game.finished("time limit expired", timeLimit);
		}
	
	/** Called to give the Manager a chance to determine if there are enough clients
		available to play the current game, and to initialize the mechanism that will
		assign clients to graph nodes. (It searches for all clients that are currently
		running a waitgame and makes a list of them.)
		This is publicly exposed so that it can be overridden if a different policy is desired.
		@return a flag indicating there are sufficient players available.
		*/
	public boolean ready(int required) { 
		list= new Vector<ClientLiaison>(host.clients.size()); 
		for (int i=0; i<host.clients.size(); i++) {
			ClientLiaison client= host.clients.elementAt(i);
			if (client.game==host.waitgame) 
				list.add(client);
			}
		int missing= required-list.size();
		if (missing>0) {
			// warning: BNSGameControllers call this while running on Event Dispatch Thread,
			// but this dialog blocks until acknowledged.
			// TODO: Release the thread and accomplish on a different thread.
			host.waitgame.add("\nwaiting for " + required + "-" + list.size() + "="+ missing +" more clients to join\n");
			JOptionPane.showMessageDialog(null, 
										  "waiting for "+ missing +" more clients to join",
										  "alert", JOptionPane.ERROR_MESSAGE);
			list= null;
			return false;
			}
		rand= new Random();
		return true;
		}
	
	/** Assigns a client to the given node at random from amongst the ones on the list 
		of clients discovered during the call to ready() to be available for play.
		This is publicly exposed so that it can be overridden if a different policy is desired.
		*/
	public ClientLiaison assign(Node node) {
		return list.remove(rand.nextInt(list.size()));
		}
	
	/** Assigns every node to a different client.
		@param gameController the BNSGame that will be played.
		@param nodes an Enumeration of all Nodes that need to be given a client.
		*/
	public synchronized boolean assignClients(BNSGameController gameController,Enumeration<Node> nodes) {
		assert (game==gameController);	// TODO: fix for running multiple games
		//int gameSize= gameController.getSize();
		//System.err.println("loading game "+ game.getInfo());
		// warning: the BNSGameControllers call this method while running on Event Dispatch Thread.
		// TODO: If this method evolves to be quite slow (e.g. it requires human involvement), 
		// then the thread must be released and the work accomplished on a different thread.
		synchronized(host.clients) {
			if (!ready(game.getSize())) return false;	// TODO: fix for running multiple games
			game.gameLog.print("clid");
			while (nodes.hasMoreElements()) {
				Node node= nodes.nextElement();
				ClientLiaison client= assign(node);
				synchronized(client) {
					client.adoptGame(game, node);
					client.sendMessage("loading "+ game.getTitle());
					game.gameLog.print(" "+client.clientID+"="+client.playerName);
					}
				System.err.println("client "+client.playerName+" has node "+node.lbl);
				}
			game.gameLog.println();
			}
		host.sessionLog.println("loaded game "+ gameController.getInfo());
		host.sessionLog.println("saving events in "+ Library.getGameLogName());
		host.supervisor.repaint();
		return true;
		}
	
	/** Shuts down a game by reclaiming all its clients and directing them to waitgame instead.
		Also, this method records the payoff of each player in sessionLog. 
		*/
	public void closeGame() {
		synchronized(host.clients) {// switch everyone back to a wait state
			host.waitgame.clear();	// TODO: rethink this!
			for (int clientNum= 0; clientNum<host.clients.size(); clientNum++) {
				ClientLiaison client= host.clients.elementAt(clientNum);
				if (client==null || client.game!=game) continue;
				synchronized(client) {
					client.adoptGame(host.waitgame,null);
					}
				}
			}
		host.supervisor.repaint();
		host.waitgame.add(" experiment "+ game.gameValue()+"\n");
		host.waitgame.add(game.getTitle()+ " experiment over.\n");
		host.sessionLog.println("--------------------------------------------------------------");
		// TODO: wait until all personas switched
		game= null;
		host.game= null;	// TODO: fix to handle multiple games
		}
	
	/** puts a short message in the status field of each client, 
		which will be sent to the client on the next call to passUpdate().
		The Client Persona might be built to display this in a message bar.
		The current implementation assumes there is only one active game. */
	public void bulletin(String message) {
		synchronized(host.clients) {// for each client playing the active game
			for (int clientNum= 0; clientNum<host.clients.size(); clientNum++) {
				ClientLiaison client= host.clients.elementAt(clientNum);
				if (client==null || client.game!=game || client.empty) continue;
				client.sendMessage(message);
				}
			}
		}
	
	}
